﻿module Supernova {

    export class Effects {

        public available: Array<Effect> = [];

        public active: Array<Effect> = [];

        public find(name: string) {
            for (var i in this.available) {
                var effect = this.available[i];
                if (effect.name == name) return effect;
            }
            return null;
        }

        public select(f: (e: Effect) => boolean): Array<Effect> {
            var result = [];
            for (var i in this.active) {
                var effect = this.active[i];
                if (f(effect)) result.push(effect);
            }
            return result;
        }

        public execute(f: (e: Effect) => boolean, action: (e: Effect) => void) {
            var effects = this.select(f);
            for (var i in effects) {
                action(effects[i]);
            }
        }

        public static create(): Effects {
            var effects = new Effects();
            effects.available = [];

            // If no galaxy (i.e. trait selection) then create temporary stats
            var profile = galaxy !== null ? galaxy.profile : new GeneratorProfile('Temporary');

            // Facilities
            effects.available.push(new SimpleEffect(EffectCategory.researchFacility,
                'Communications Array',
                ['Regularly produces ◍ for each population point.', 'Old Income: ◍ ' + profile.gold.rate.noComms + '/s per 🚶', 'New Income: ◍ ' + profile.gold.rate.withComms + '/s per 🚶'],
                function () { galaxy.research.facilities.comms = true; }));

            effects.available.push(new SimpleEffect(EffectCategory.researchFacility,
                'Scanner',
                ['Makes enemies visible when they are close to a planet.', 'Old Range: ' + config.scanning.range.noFacility, 'New Range: ' + config.scanning.range.withFacility, 'Unlocks:   "Efficient Scanners".'],
                function () { galaxy.research.facilities.scanner = true; },
                function () { return [efficientScanners]; }));

            effects.available.push(new SimpleEffect(EffectCategory.researchFacility,
                'Mining Base',
                ['Allows ◍ under the crust of a planet to be mined.', 'Income: ◍ ' + profile.gold.rate.withMines + ' per second.'],
                function () { galaxy.research.facilities.mining = true; }));

            effects.available.push(new SimpleEffect(EffectCategory.researchFacility,
                'Orbital',
                ['Defends a planet against enemies and artillery shells.'],
                function () { galaxy.research.facilities.orbital = true; }));

            var planetaryArtilleryStrength = new SimpleEffect(EffectCategory.researchPassive,
                'Artillery Double Strength',
                ['Planetary artillery shells are twice as effective.', 'Old Strength: 1', 'New Strength: 2'],
                function () { galaxy.research.artillery.planetary.strength *= 2; });

            var planetaryArtilleryIgnoreOrbitals = new SimpleEffect(EffectCategory.researchPassive,
                'Artillery Ignores Orbitals',
                ['Planetary artillery pods ignore orbitals.'],
                function () { galaxy.research.artillery.planetary.ignoreOrbitals = true; });

            var planetaryArtilleryRange = new IncreasePlanetaryArtilleryRangeEffect();

            var planetaryArtillery = new SimpleEffect(EffectCategory.researchFacility,
                'Artillery Launcher',
                ['Regularly launches artillery pods to depopulate nearby enemy planets.', 'Strength: 1', 'Unlocks:  "Improved Artillery Range".', 'Unlocks:  "Artillery Double Strength".', 'Unlocks:  "Artillery Ignores Orbitals".'],
                function () { galaxy.research.artillery.planetary.enabled = true; if (channel.online) channel.invoke.chat(channel.playerName + ' has researched planet-based artillery.'); },
                function () { return [planetaryArtilleryRange, planetaryArtilleryStrength, planetaryArtilleryIgnoreOrbitals]; });

            // Passive effects
            effects.available.push(new SimpleEffect(EffectCategory.researchPassive,
                'Reclaimation',
                ['Reclaim 0 - 50% of the ◍ spent on ships when disbanding them, based on armor.'],
                function () { galaxy.research.passive.reclaim = true; }));

            effects.available.push(new CheaperShipsEffect());
            
            var efficientTerraforming = new SimpleEffect(EffectCategory.researchPassive,
                'Efficient Terraforming',
                ['Terraforming consumes 75% less materials.'],
                function () { galaxy.research.passive.efficientTerraforming = true; });

            var efficientScanners = new SimpleEffect(EffectCategory.researchPassive,
                'Efficient Scanners',
                ['Scanning range of planetary scanners increased by 25%.', 'Old Range: ' + config.scanning.range.withFacility, 'New Range: ' + config.scanning.range.withFacility
                    * 1.25],
                function () { galaxy.research.passive.efficientScanners = true; });

            var refining = new SimpleEffect(EffectCategory.researchPassive,
                'Refining',
                ['All mining ships refine extracted ◍ so that it is worth 25% more.'],
                function () { galaxy.research.passive.refining = true; });

            var freeArmor = new FreeArmorEffect();

            var freeAttack = new FreeAttackEffect();

            var shipArtillery2 = new SimpleEffect(EffectCategory.researchPassive,
                'Additional Artillery Pods',
                ['Artillery capable ships now start with five artillery pods.', 'Old Count: 2', 'New Count: 5'],
                function () { galaxy.research.artillery.ship.pods = 5; });

            var shipArtillery1 = new SimpleEffect(EffectCategory.researchPassive,
                'Artillery Pods',
                ['All new attack ships equipped with two bombard slots will have two artillery pods.', 'Artillery pods are launched automatically and depopulate planets from a distance.', 'Unlocks: "Additional Artillery Pods".', 'Unlocks: "Planetary Artillery Launcher".'],
                function () { galaxy.research.artillery.ship.enabled = true, galaxy.research.artillery.ship.pods = 2; if (channel.online) channel.invoke.chat(channel.playerName + ' has researched ship-based artillery.'); },
                function () { return [shipArtillery2, planetaryArtillery]; });

            // Ship actions
            effects.available.push(new SimpleEffect(EffectCategory.researchAction,
                'Automated Explore',
                ['Instruct ships to navigate to unexplored systems and collect goodies automatically.'],
                function () { galaxy.research.actions.explore = true; }));

            effects.available.push(new SimpleEffect(EffectCategory.researchAction,
                'Repair',
                ['Instruct ships to stop and repair themselves.'],
                function () { galaxy.research.actions.repair = true; }));

            var draft = new SimpleEffect(EffectCategory.researchAction,
                'Draft',
                ['Ability to sacrifice two population points on a planet to produce a single attack ship.', 'The ship will be assigned all currently available attack slots.', 'Note that the ship will not have any other slots assigned.'],
                function () { galaxy.research.actions.draft = true; gameUI.createShipPanel.draftButton.visible = true; });

            // Ship design
            effects.available.push(new UnlockSlotEffect('Colonisation',
                ['Each level is a population point transported to a planet for colonisation.'],
                gameUI.createShipPanel.populationSlider));

            effects.available.push(new UnlockSlotEffect('Terraforming',
                ['Transform barren planets into habitable ones.', 'Unlocks: "Efficient Terraforming".'],
                gameUI.createShipPanel.terraformingSlider,
                function () { var e = []; if (gameUI.createShipPanel.terraformingSlider.enabledValues == 1) e.push(efficientTerraforming); return e; }));

            effects.available.push(new UnlockSlotEffect('Scanning',
                ['Increases the range by which enemies can be detected.'],
                gameUI.createShipPanel.scanningSlider));

            effects.available.push(new UnlockSlotEffect('Speed',
                ['Each level substantially increases the speed by which ships can move.'],
                gameUI.createShipPanel.speedSlider));

            effects.available.push(new UnlockSlotEffect('Armor',
                ['Makes your ships more difficult to destroy.', 'Planets without orbitals do not destroy bombarding ships from level 2.', 'Planets with orbitals do not destroy bombarding ships from level 3.', 'Unlocks: "Free Armor" at level 3.'],
                gameUI.createShipPanel.armorSlider,
                function () { var e = []; if (gameUI.createShipPanel.armorSlider.enabledValues == 3) e.push(freeArmor); return e; }));

            effects.available.push(new UnlockSlotEffect('Shields',
                ['Regenerative shields absorb damage, preventing it from affecting armor.', 'Shields are ineffective when bombarding.'],
                gameUI.createShipPanel.shieldsSlider));

            effects.available.push(new UnlockSlotEffect('Attack',
                ['Attack ships with increasing amounts of power.', 'Unlocks: "Free Attack" at level 2.', 'Unlocks: "Draft" at level 3.'],
                gameUI.createShipPanel.attackSlider,
                function () { var e = []; if (gameUI.createShipPanel.attackSlider.enabledValues == 2) e.push(freeAttack); if (gameUI.createShipPanel.attackSlider.enabledValues == 3) e.push(draft); return e; }));

            effects.available.push(new UnlockSlotEffect('Invade',
                ['Remove enemy population points from a planet.', 'Unlocks: "Artillary Pods" at level 2'],
                gameUI.createShipPanel.bombardSlider,
                function () { var e = []; if (gameUI.createShipPanel.bombardSlider.enabledValues == 2) e.push(shipArtillery1); return e; }));

            effects.available.push(new UnlockSlotEffect('Mining',
                ['Extract ◍ from planets.', 'Unlocks: "Refining" at level 2.'],
                gameUI.createShipPanel.miningSlider,
                function () { var e = []; if (gameUI.createShipPanel.miningSlider.enabledValues == 2) e.push(refining); return e; }));

            // Traits
            effects.available.push(new FreeScannerEffect());
            effects.available.push(new EnhancedShipyardsEffect());
            effects.available.push(new FreeOrbitalsEffect());
            effects.available.push(new SpaceStationsEffect());
            effects.available.push(new ScholarsEffect());
            effects.available.push(new HomesteadingEffect());
            effects.available.push(new UndergroundCitiesEffect());
            effects.available.push(new FightingSpiritEffect());
            effects.available.push(new DomeCitiesEffect());
            effects.available.push(new HyperIntelligenceEffect());
            effects.available.push(new SalvagersEffect());
            effects.available.push(new IndustriousEffect());
            effects.available.push(new FreeShieldsEffect());
            effects.available.push(new RapidArtilleryEffect());
            effects.available.push(new OverpopulationEffect());
            return effects;
        }

        public activate(effect: Effect) {

            // Call the activate method to activate the effect
            var move = true;
            if (effect.activate !== undefined) {
                var results = effect.activate();
                if (results.allowFurtherActivations) {
                    move = false;
                }
            }

            // Move the effect into the set of activated effects
            if (move) {
                var index = this.available.indexOf(effect);
                this.available.splice(index, 1);
                this.active.push(effect);
            }

            // Allow unlocking of additional effects
            if (effect.unlockAdditionalEffects !== undefined) {
                this.available = this.available.concat(effect.unlockAdditionalEffects());
            }

            // Confirm to the user the effect has been unlocked
            Notices.addMessage(MessageType.information, 'Unlocked: ' + effect.name);
        }
    }

    export class UnlockSlotEffect extends Effect {

        private slider: UI.Slider;

        constructor(name: string, helpText: string[], slider: UI.Slider, unlocks: () => Array<Effect> = undefined) {
            super();
            this.name = name;
            this.helpText = helpText;
            this.slider = slider;
            this.category = EffectCategory.researchShip;
            this.activate = this.activateInternal;
            this.getFurtherHelpText = this.getFurtherHelpTextInternal;
            if (unlocks !== undefined) {
                this.unlockAdditionalEffects = unlocks;
            }
        }

        private activateInternal() {
            if (this.slider.enabledValues < this.slider.maxValue) {
                this.slider.enabledValues += 1;
            }
            return { allowFurtherActivations: this.slider.enabledValues < this.slider.maxValue };
        }

        private getFurtherHelpTextInternal() {
            var result = [];
            var currentLevel = this.slider.enabledValues;
            var stats = [gameUI.createShipPanel.getStats([this.slider], currentLevel), gameUI.createShipPanel.getStats([this.slider], currentLevel + 1)];
            for (var i = 0; i < 2; i++) {
                result.push('');
                result.push(i == 0 ? 'Current Level (' + currentLevel + ')' : 'Next Level (' + (currentLevel + 1) + ')');
                if (stats[i].length > 0) {
                    result.push('');
                    for (var j = 0; j < stats[i].length; j++) {
                        result.push(stats[i][j]);
                    }
                }
            }
            return result;
        }
    }

    export class SimpleEffect extends Effect {

        public action: () => void;

        constructor(category: EffectCategory, name: string, helpText: Array<string>, action: () => void, unlocks: () => Array<Effect> = undefined) {
            super();
            this.category = category;
            this.name = name;
            this.helpText = helpText;
            this.action = action;
            this.activate = this.activateInternal;
            if (unlocks !== undefined) {
                this.unlockAdditionalEffects = unlocks;
            }
        }

        private activateInternal() {
            this.action();
            return { allowFurtherActivations: false };
        }

    }

    export class CheaperShipsEffect extends Effect {

        public category: EffectCategory = EffectCategory.researchPassive;

        public name: string = 'Cheaper Ships';

        public helpText: Array<string> = ['Ships cost 15% less ◍ than normal.'];

        constructor() {
            super();
            this.calculateShipCost = this.calculateShipCostInternal;
        }

        private calculateShipCostInternal(): { priceReduction: number } {
            return { priceReduction: 0.15 };
        }

    }

    export class FreeArmorEffect extends Effect {

        public category: EffectCategory = EffectCategory.researchPassive;

        public name: string = 'Free Armor';

        public helpText: Array<string> = ['All newly constructed ships start with an additional free armor slot.'];

        constructor() {
            super();
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            var slider = gameUI.createShipPanel.armorSlider;
            if (slider.minValue < slider.visibleValues) {
                slider.minValue++;
                slider.value = Math.min(slider.enabledValues, slider.value + 1);
            }
            return { allowFurtherActivations: false };
        }

    }

    export class FreeAttackEffect extends Effect {

        public category: EffectCategory = EffectCategory.researchPassive;

        public name: string = 'Free Attack';

        public helpText: Array<string> = ['All newly constructed ships start with a free attack slot.'];

        constructor() {
            super();
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            var slider = gameUI.createShipPanel.attackSlider;
            if (slider.minValue < slider.visibleValues) {
                slider.minValue++;
                slider.value = Math.min(slider.enabledValues, slider.value + 1);
            }
            return { allowFurtherActivations: false };
        }

    }

    export class IncreasePlanetaryArtilleryRangeEffect extends Effect {

        public category: EffectCategory = EffectCategory.researchPassive;

        public name: string = 'Improved Artillery Range';

        public helpText: Array<string> = ['Doubles planetary artillery range.'];

        private timesUsed: number = 0;

        constructor() {
            super();
            this.activate = this.activateInternal;
            this.getFurtherHelpText = this.getFurtherHelpTextInternal;
        }

        private activateInternal() {
            this.timesUsed++;
            galaxy.research.artillery.planetary.radius += config.artillery.radiusIncrement;
            return { allowFurtherActivations: this.timesUsed < 3 };
        }

        private getFurtherHelpTextInternal() {
            return ['Current Range: ' + galaxy.research.artillery.planetary.radius, 'New Range:     ' + (galaxy.research.artillery.planetary.radius + config.artillery.radiusIncrement)];
        }

    }

    export class TraitEffect extends Effect {

        public category: EffectCategory = EffectCategory.trait;

        public name: string;

        public pointsRequired: number = 0;

        constructor(name: string, helpText: string, pointsRequired: number) {
            super();
            this.name = name;
            this.helpText = [helpText];
            this.pointsRequired = pointsRequired;
        }

    }

    export class FreeScannerEffect extends TraitEffect {

        constructor() {
            super('Free Scanner', 'Scanners are available immediately, and a scanner is installed on the home planet.', 25);
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            galaxy.effects.activate(galaxy.effects.find('Scanner'));
            galaxy.home.body.facilities.scanner = true;
            channel.invoke.updateBodyFacility(galaxy.home.system.code, galaxy.home.body.code, 'scanner');
            return { allowFurtherActivations: false };
        }

    }

    export class EnhancedShipyardsEffect extends TraitEffect {

        constructor() {
            super('Enhanced Shipyards', 'Shipyards repair ships and can restock terraforming materials.', 25);
            this.shipLanded = this.shipLandedInternal;
        }

        private shipLandedInternal(ship: Ship) {
            if (ship.currentLocation.body !== null && ship.currentLocation.body.system.owner == OwnerType.human && ship.owner == OwnerType.human && ship.currentLocation.body.facilities.shipyard) {
                if (ship.armor < ship.maxArmor) {
                    ship.stopAction();
                    ship.repairAction();
                }
                if (ship.terraforming < ship.preset.terraform) {
                    ship.terraforming = ship.preset.terraform;
                }
            }
        }

    }

    export class FreeOrbitalsEffect extends TraitEffect {

        constructor() {
            super('Free Orbitals', 'Orbitals are available immediately, and all newly colonised planets get 1 free orbital.', 25);
            this.activate = this.activateInternal;
            this.shipActionPerformed = this.shipActionPerformedInternal;
        }

        private activateInternal() {
            galaxy.effects.activate(galaxy.effects.find('Orbital'));
            galaxy.home.body.facilities.orbitals++;
            channel.invoke.updateBodyFacility(galaxy.home.system.code, galaxy.home.body.code, 'orbital', galaxy.home.body.facilities.orbitals);
            return { allowFurtherActivations: false };
        }

        private shipActionPerformedInternal(ship: Ship, action: ShipAction) {
            if (ship.owner == OwnerType.human && action == ShipAction.colonise) {
                ship.currentLocation.body.facilities.orbitals++;
                channel.invoke.updateBodyFacility(ship.currentLocation.body.system.code, ship.currentLocation.body.code, 'orbital', ship.currentLocation.body.facilities.orbitals);
            }
        }

    }

    export class SpaceStationsEffect extends TraitEffect {

        constructor() {
            super('Space Stations', 'Communications Arrays act as space stations, increasing planet\'s maximum 🚶 by 1.', 25);
            this.facilityConstructed = this.facilityConstructedInternal;
        }

        private facilityConstructedInternal(body: Body, facility: string) {
            if (facility == 'comms' && body.system.owner == OwnerType.human) {
                body.maxPopulation++;
                channel.invoke.updateBodyMaxPopulation(body.system.code, body.code, body.maxPopulation);
            }
        }

    }

    export class ScholarsEffect extends TraitEffect {

        constructor() {
            super('Scholars', 'Gain 10 ⓢ for each Research Center built.', 25);
            this.facilityConstructed = this.facilityConstructedInternal;
        }

        private facilityConstructedInternal(body: Body, facility: string) {
            if (facility == 'science' && body.system.owner == OwnerType.human) {
                galaxy.addScience(10);
                Notices.addToast(body.x, body.y, '+ ⓢ 10');
            }
        }

    }

    export class HomesteadingEffect extends TraitEffect {

        constructor() {
            super('Homesteading', 'Gain 250 ◍ when a planet is colonised.', 50);
            this.shipActionPerformed = this.shipActionPerformedInternal;
        }

        private shipActionPerformedInternal(ship: Ship, action: ShipAction) {
            if (ship.owner == OwnerType.human && action == ShipAction.colonise) {
                galaxy.gold += 250;
                Notices.addToast(ship.currentLocation.x, ship.currentLocation.y, '+ #250');
            }
        }

    }

    export class UndergroundCitiesEffect extends TraitEffect {

        constructor() {
            super('Underground Cities', 'Mining Bases increase planet\'s maximum 🚶 by 2.', 50);
            this.facilityConstructed = this.facilityConstructedInternal;
        }

        private facilityConstructedInternal(body: Body, facility: string) {
            if (facility == 'mine' && body.system.owner == OwnerType.human) {
                body.maxPopulation += 2;
                channel.invoke.updateBodyMaxPopulation(body.system.code, body.code, body.maxPopulation);
            }
        }

    }

    export class FightingSpiritEffect extends TraitEffect {

        constructor() {
            super('Fighting Spirit', 'Home planet starts with two free attack ships.', 50);
            this.activate = this.activateInternal;
        }

        private activateInternal() {

            // Create a preset
            var preset = new ShipPreset();
            preset.scanning = 1;
            preset.speed = 1;
            preset.armor = 1;
            preset.attack = 1;

            // Create the ships
            for (var i = 0; i < 2; i++) {
                var ship = Ship.create(galaxy.home.body, preset.clone(), 0, OwnerType.human, 'Free Attack Ship');
                ship.code = channel.nextCode++;
                channel.invoke.createPresetShips(1, galaxy.home.system.code, galaxy.home.body.code, ship.code, preset, 0, ship.name, 0);
            }
            return { allowFurtherActivations: false };
        }

    }

    export class DomeCitiesEffect extends TraitEffect {

        constructor() {
            super('Dome Cities', 'Gas giants can be terraformed to support a small population.', 50);
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            galaxy.research.actions.terraformGasGiants = true;
            return { allowFurtherActivations: false };
        }

    }

    export class HyperIntelligenceEffect extends TraitEffect {

        constructor() {
            super('Hyper Intelligence', 'Start the game with twice as many initial research points.', 75);
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            galaxy.science *= 2;
            return { allowFurtherActivations: false };
        }

    }

    export class SalvagersEffect extends TraitEffect {

        constructor() {
            super('Salvagers', 'Salvage 20% the ◍ spent on enemy ships when they are destroyed.', 75);
            this.shipDestroyed = this.shipDestroyedInternal;
        }

        private shipDestroyedInternal(attacker: Ship, victim: Ship) {
            if (attacker.owner == OwnerType.human && attacker.owner != victim.owner) {
                var amount = Math.floor(victim.cost * 0.2);
                galaxy.gold += amount;
                Notices.addToast(victim.currentLocation.x, victim.currentLocation.y, '+ ◍ ' + amount);
            }
            return { allowFurtherActivations: false };
        }

    }

    export class IndustriousEffect extends TraitEffect {

        constructor() {
            super('Industrious', 'Home planet has lots of ◍ and a mining base.', 75);
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            galaxy.home.body.gold = Math.floor(galaxy.profile.resources.goldMaximum * 0.5);
            galaxy.effects.activate(galaxy.effects.find('Mining Base'));
            galaxy.home.body.facilities.mine = true;
            channel.invoke.updateBodyFacility(galaxy.home.system.code, galaxy.home.body.code, 'mine');
            return { allowFurtherActivations: false };
        }

    }

    export class FreeShieldsEffect extends TraitEffect {

        constructor() {
            super('Free Shields', 'All constructed ships start with a free shield slot.', 100);
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            galaxy.effects.activate(galaxy.effects.find('Shields'));
            var slider = gameUI.createShipPanel.shieldsSlider;
            if (slider.minValue < slider.visibleValues) {
                slider.minValue++;
                slider.value++;
            }
            return { allowFurtherActivations: false };
        }

    }

    export class RapidArtilleryEffect extends TraitEffect {

        constructor() {
            super('Rapid Artillery', 'Planet-based and ship-based artillery fires twice as often.', 100);
            this.activate = this.activateInternal;
        }

        private activateInternal() {
            galaxy.research.artillery.planetary.wait /= 2;
            galaxy.research.artillery.ship.wait /= 2;
            return { allowFurtherActivations: false };
        }

    }

    export class OverpopulationEffect extends TraitEffect {

        constructor() {
            super('Overpopulation', 'Start with two populated planets rather than one.', 100);
            this.activate = this.activateInternal;
        }

        private activateInternal() {

            // Find an already existing body we can repurpose
            var candidate = findSystemBody(galaxy.home.system, (body) => { return body.type == BodyType.habitable && body.population == 0; });
            if (candidate == null) {
                candidate = findSystemBody(galaxy.home.system, (body) => { return body.type == BodyType.barren; });
            }
            if (candidate == null) {
                candidate = findSystemBody(galaxy.home.system, (body) => { return body.type == BodyType.gas; });
            }

            // Convert the planet if necessary
            if (candidate != null) {
                if (candidate.type != BodyType.habitable) {
                    candidate.type = BodyType.habitable;
                    channel.invoke.updateBodyType(candidate.system.code, candidate.code, candidate.type);
                }

                // Update maximum population if necessary
                if (candidate.maxPopulation < galaxy.home.body.population) {
                    candidate.maxPopulation = galaxy.home.body.population;
                    channel.invoke.updateBodyMaxPopulation(candidate.system.code, candidate.code, candidate.maxPopulation);
                }

                // Populate the planet
                candidate.population = galaxy.home.body.population;
                channel.invoke.updateBodyPopulation(candidate.system.code, candidate.code, candidate.population);
            }
            return { allowFurtherActivations: false };
        }

    }

} 